/*
 * Copyright 2005-2007 Noelios Consulting.
 * 
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License (the "License"). You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the license at
 * http://www.opensource.org/licenses/cddl1.txt See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL HEADER in each file and
 * include the License file at http://www.opensource.org/licenses/cddl1.txt If
 * applicable, add the following below this CDDL HEADER, with the fields
 * enclosed by brackets "[]" replaced with your own identifying information:
 * Portions Copyright [yyyy] [name of copyright owner]
 */

package com.noelios.restlet;

import java.util.logging.Level;
import java.util.logging.Logger;

import org.restlet.Context;
import org.restlet.Filter;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.service.LogService;
import org.restlet.util.Template;

import com.noelios.restlet.util.IdentClient;

/**
 * Filter logging all calls after their handling by the target Restlet. The
 * current format is similar to IIS 6 logs. The logging is based on the
 * java.util.logging package.
 * 
 * @see <a
 *      href="http://www.restlet.org/documentation/1.0/tutorial#part07">Tutorial:
 *      Filters and call logging</a>
 * @author Jerome Louvel (contact@noelios.com)
 */
public class LogFilter extends Filter {
	/** Obtain a suitable logger. */
	private Logger logger;

	/** The log template to use. */
	protected Template logTemplate;

	/** The log service. */
	protected LogService logService;

	/**
	 * Constructor.
	 * 
	 * @param context
	 *            The context.
	 * @param logService
	 *            The log service descriptor.
	 */
	public LogFilter(Context context, LogService logService) {
		super(context);
		this.logService = logService;

		if (logService != null) {
			this.logger = Logger.getLogger(logService.getLoggerName());
			this.logTemplate = (logService.getLogFormat() == null) ? null
					: new Template(getLogger(), logService.getLogFormat());
		}
	}

	/**
	 * Allows filtering before processing by the next Restlet. Save the start
	 * time.
	 * 
	 * @param request
	 *            The request to handle.
	 * @param response
	 *            The response to update.
	 */
	@Override
	protected void beforeHandle(Request request, Response response) {
		request.getAttributes().put("org.restlet.startTime",
				System.currentTimeMillis());
	}

	/**
	 * Allows filtering after processing by the next Restlet. Log the call.
	 * 
	 * @param request
	 *            The request to handle.
	 * @param response
	 *            The response to update.
	 */
	@Override
	protected void afterHandle(Request request, Response response) {
		long startTime = (Long) request.getAttributes().get(
				"org.restlet.startTime");
		int duration = (int) (System.currentTimeMillis() - startTime);

		// Format the call into a log entry
		if (this.logTemplate != null) {
			this.logger.log(Level.INFO, format(request, response));
		} else {
			this.logger.log(Level.INFO, formatDefault(request, response,
					duration));
		}
	}

	/**
	 * Format a log entry using the default format.
	 * 
	 * @param request
	 *            The request to log.
	 * @param response
	 *            The response to log.
	 * @param duration
	 *            The call duration (in milliseconds).
	 * @return The formatted log entry.
	 */
	protected String formatDefault(Request request, Response response,
			int duration) {
		StringBuilder sb = new StringBuilder();
		long currentTime = System.currentTimeMillis();

		// Append the date of the request
		sb.append(String.format("%tF", currentTime));
		sb.append('\t');

		// Append the time of the request
		sb.append(String.format("%tT", currentTime));
		sb.append('\t');

		// Append the client IP address
		String clientAddress = request.getClientInfo().getAddress();
		sb.append((clientAddress == null) ? "-" : clientAddress);
		sb.append('\t');

		// Append the user name (via IDENT protocol)
		if (this.logService.isIdentityCheck()) {
			IdentClient ic = new IdentClient(getLogger(), request
					.getClientInfo().getAddress(), request.getClientInfo()
					.getPort(), response.getServerInfo().getPort());
			sb.append((ic.getUserIdentifier() == null) ? "-" : ic
					.getUserIdentifier());
		} else {
			sb.append('-');
		}
		sb.append('\t');

		// Append the server IP address
		String serverAddress = response.getServerInfo().getAddress();
		sb.append((serverAddress == null) ? "-" : serverAddress);
		sb.append('\t');

		// Append the server port
		Integer serverport = response.getServerInfo().getPort();
		sb.append((serverport == null) ? "-" : serverport.toString());
		sb.append('\t');

		// Append the method name
		String methodName = request.getMethod().getName();
		sb.append((methodName == null) ? "-" : methodName);

		// Append the resource path
		sb.append('\t');
		String resourcePath = request.getResourceRef().getPath();
		sb.append((resourcePath == null) ? "-" : resourcePath);

		// Append the resource query
		sb.append('\t');
		String resourceQuery = request.getResourceRef().getQuery();
		sb.append((resourceQuery == null) ? "-" : resourceQuery);

		// Append the status code
		sb.append('\t');
		sb.append((response.getStatus() == null) ? "-" : Integer
				.toString(response.getStatus().getCode()));

		// Append the returned size
		sb.append('\t');
		if (response.getEntity() == null) {
			sb.append('0');
		} else {
			sb.append((response.getEntity().getSize() == -1) ? "-" : Long
					.toString(response.getEntity().getSize()));
		}

		// Append the received size
		sb.append('\t');
		if (request.getEntity() == null) {
			sb.append('0');
		} else {
			sb.append((request.getEntity().getSize() == -1) ? "-" : Long
					.toString(request.getEntity().getSize()));
		}

		// Append the duration
		sb.append('\t');
		sb.append(duration);

		// Append the host reference
		sb.append('\t');
		sb.append((request.getHostRef() == null) ? "-" : request.getHostRef()
				.toString());

		// Append the agent name
		sb.append('\t');
		String agentName = request.getClientInfo().getAgent();
		sb.append((agentName == null) ? "-" : agentName);

		// Append the referrer
		sb.append('\t');
		sb.append((request.getReferrerRef() == null) ? "-" : request
				.getReferrerRef().getIdentifier());

		return sb.toString();
	}

	/**
	 * Format a log entry.
	 * 
	 * @param request
	 *            The request to log.
	 * @param response
	 *            The response to log.
	 * @return The formatted log entry.
	 */
	protected String format(Request request, Response response) {
		return this.logTemplate.format(request, response);
	}

}